package cn.tp.ksoft.civil_driver.view.widget.tablayou;


import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.util.Pools;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPager;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.Interpolator;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;

import cn.tp.ksoft.civil_driver.R;

import static android.support.v4.view.ViewPager.SCROLL_STATE_DRAGGING;
import static android.support.v4.view.ViewPager.SCROLL_STATE_IDLE;
import static android.support.v4.view.ViewPager.SCROLL_STATE_SETTLING;

/**
 * Created by zhusong on 16/4/26.
 */
public class ITabLayout extends HorizontalScrollView {


    private final float DEFAULT_HEIGHT = 48f; //dp
    private final int DEFAULT_INDICATOR_HEIGHT = 2;//4dp

    // mode
    private int mMode;
    private static final int MODE_FIXED = 0;
    private static final int MODE_SCROLLABLE = 1;
    @IntDef(value = {MODE_FIXED, MODE_SCROLLABLE})
    @Retention(RetentionPolicy.SOURCE)
    private @interface Mode{}

    // gravity
    private int mTabGravity;
    private static final int GRAVITY_FILL = 0;
    private static final int GRAVITY_CENTER = 1;
    @IntDef(value = {GRAVITY_FILL, GRAVITY_CENTER})
    @Retention(RetentionPolicy.SOURCE)
    private @interface TabGravity{}

    // tab item container
    private ISlidingTabStrip mTabStrip;
    private int heightForPadding;
    final Interpolator interpolator = new FastOutSlowInInterpolator();
    private DisplayMetrics mDm = getResources().getDisplayMetrics();
    private ValueAnimator mScrollAnimator;
    private static final int ANIMATION_DURATION = 300;
    private final Pools.Pool<ITab> mTabPool = new Pools.SimplePool<ITab>(16);
    private final Pools.Pool<ITabView> mTabViewPool = new Pools.SynchronizedPool<>(12);
    private ArrayList<ITab> mTabs = new ArrayList<>();

    private ITab mSelectedTab;
    private OnTabSelectedListener mOnTabSelectedListener;

    private ViewPager mViewPager;
    private PagerAdapter mPagerAdapter;
    private DataSetObserver mPagerAdapterObserver;
    private TabLayoutOnPageChangeListener mPageChangeListener;

    public ITabLayout(Context context) {
        this(context, null);
    }

    public ITabLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ITabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        heightForPadding = getPaddingBottom() + getPaddingTop();
        // disable scroll bar
        setHorizontalScrollBarEnabled(false);
        // add sliding tab strip
        mTabStrip = new ISlidingTabStrip(context);
        setPadding(getPaddingLeft(), 0, getPaddingRight(), 0);
        super.addView(mTabStrip, 0, generateDefaultLayoutParams());

        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ITabLayout);
        mMode = ta.getInt(R.styleable.ITabLayout_mode, MODE_FIXED);
        mTabGravity = ta.getInt(R.styleable.ITabLayout_iTabGravity, GRAVITY_FILL);
        mTabStrip.setSelectedIndicatorColor(ta.getColor(R.styleable.ITabLayout_iTabIndicatorColor, Color.RED));
        mTabStrip.setSelectedIndicatorHeight(ta.getDimensionPixelSize(R.styleable.ITabLayout_iTabIndicatorHeight,
                Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_INDICATOR_HEIGHT, mDm))));
        int tabVpStyle = ta.getResourceId(R.styleable.ITabLayout_iTabVpItemStyle, -1);
        if (tabVpStyle > 0) {
            ITabItem.mDefaultStyleRes = tabVpStyle;
        }
        ta.recycle();

        // Now apply the tab mode and gravity
        applyModeAndGravity();
    }

    private void applyModeAndGravity() {

        switch (mMode) {
            case MODE_FIXED:
                mTabStrip.setGravity(Gravity.CENTER_HORIZONTAL);
                break;
            case MODE_SCROLLABLE:
                mTabStrip.setGravity(Gravity.LEFT);
                break;
        }

        updateTabViews(true);
    }

    private void updateTabViews(final boolean requestLayout) {
        for (int i = 0; i < mTabStrip.getChildCount(); i++) {
            View child = mTabStrip.getChildAt(i);
            child.setMinimumWidth(0);
            updateTabViewLayoutParams((LinearLayout.LayoutParams) child.getLayoutParams());
            if (requestLayout) {
                child.requestLayout();
            }
        }
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        LayoutParams params = super.generateDefaultLayoutParams();
        params.width = LayoutParams.WRAP_CONTENT;
        return params;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        // calculate height
        if (heightMode != MeasureSpec.EXACTLY) {
            final int idealHeight = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                    DEFAULT_HEIGHT,
                    mDm)) + heightForPadding;
            if (heightMode == MeasureSpec.UNSPECIFIED) {
                heightMeasureSpec = MeasureSpec.makeMeasureSpec(idealHeight, MeasureSpec.EXACTLY);
            } else if (heightMode == MeasureSpec.AT_MOST) {
                heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(idealHeight, idealHeight),
                        MeasureSpec.EXACTLY);
            }
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        boolean remeasure = false;
        final int tabStripWidth = mTabStrip.getMeasuredWidth();
        final int specWidth = getMeasuredWidth();
        switch (mMode) {
            case MODE_FIXED : {
                remeasure = tabStripWidth != specWidth;
                break;
            }

            case MODE_SCROLLABLE : {
                // similar to fillViewport
                remeasure = tabStripWidth < specWidth;
                break;
            }
        }

        if (remeasure) {
            final int tabStripHeightMeasure = getChildMeasureSpec(heightMeasureSpec,
                    getPaddingBottom() + getPaddingTop(),
                    getMeasuredHeight());

            final int tabStripWidthMeasure = MeasureSpec.makeMeasureSpec(specWidth,
                    MeasureSpec.EXACTLY);

            mTabStrip.measure(tabStripWidthMeasure, tabStripHeightMeasure);
        }
    }

    @Override
    public void addView(View child) {
        addViewInternal(child);
    }

    @Override
    public void addView(View child, int index) {
        addViewInternal(child);
    }

    @Override
    public void addView(View child, ViewGroup.LayoutParams params) {
        addViewInternal(child);
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        addViewInternal(child);
    }

    private void addViewInternal(final View child) {
        if (child instanceof ITabItem) {
            addTabFromItemView((ITabItem)child);
        } else {
            throw new IllegalArgumentException("Only ITabItem instances can be added to ITabLayout");
        }
    }

    private void addTabFromItemView(@NonNull ITabItem item) {
        final ITab tab = newTab();

        updateTab(tab, item);
        addTab(tab);
    }

    private void updateTab(@NonNull ITab tab, @NonNull ITabItem item) {
        tab.setOrientation(item.mOrientation);
        tab.setLeftPadding(item.mLeftPadding);
        tab.setTopPadding(item.mTopPadding);
        tab.setRightPadding(item.mRightPadding);
        tab.setBottomPadding(item.mBottomPadding);
        //text attributes
        tab.setText(item.mText);
        tab.setTextSize(item.mTextSize);
        tab.setTextColors(item.mTextColors);
        tab.setTextAppearance(item.mTextAppearance);

        // icon attributes
        tab.setIcon(item.mIcon);
        tab.setIconSize(item.mIconSize);
        tab.setIconLeftMargin(item.mIconLeftMargin);
        tab.setIconTopMargin(item.mIconTopMargin);
        tab.setIconRightMargin(item.mIconRightMargin);
        tab.setIconBottomMargin(item.mIconBottomMargin);

        tab.updateTabView();
    }

    public ITab newTab() {
        ITab tab = mTabPool.acquire();
        if (null == tab) {
            tab = new ITab();
        }

        tab.mParent = this;
        tab.mView = createTabView(tab);
        return tab;
    }

    private ITabView createTabView(@NonNull ITab tab) {
        ITabView tabView = mTabViewPool.acquire();
        if (tabView == null) {
            tabView = new ITabView(getContext());
        }

        tabView.mTab = tab;
        tabView.setFocusable(true);
        return tabView;
    }

    public void addTab(@NonNull ITab tab) {
        addTab(tab, mTabs.isEmpty());
    }
    public void addTab(@NonNull ITab tab, boolean setSelected) {
        if (tab.mParent != this) {
            throw new IllegalArgumentException("Tab belongs to a different TabLayout.");
        }
        addTabView(tab, setSelected);
        configureTab(tab, mTabs.size());
        if (setSelected) {
            tab.select();
        }
    }

    private void addTabView(ITab tab, boolean setSelected) {
        final ITabView tabView = tab.mView;

        mTabStrip.addView(tabView, createLayoutParamsForTabs());
        if (setSelected) {
            tabView.setSelected(true);
        }
    }

    private void addTabView(ITab tab, int position, boolean setSelected) {
        final ITabView tabView = tab.mView;
        mTabStrip.addView(tabView, position, createLayoutParamsForTabs());
        if (setSelected) {
            tabView.setSelected(true);
        }
    }

    private LinearLayout.LayoutParams createLayoutParamsForTabs() {
        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
                LayoutParams.MATCH_PARENT);
        updateTabViewLayoutParams(lp);

        return lp;
    }

    private void updateTabViewLayoutParams(LinearLayout.LayoutParams lp) {
        if (mMode == MODE_FIXED && mTabGravity == GRAVITY_FILL) {
            lp.width = 0;
            lp.weight = 1;
        } else {
            lp.width = LinearLayout.LayoutParams.WRAP_CONTENT;
            lp.weight = 0;
        }
    }


    private void configureTab(ITab tab, int position) {
        tab.setPosition(position);
        mTabs.add(position, tab);

        final int count = mTabs.size();
        for (int i = position + 1; i < count; i++) {
            mTabs.get(i).setPosition(i);
        }
    }
    void selectTab(ITab tab) {
        selectTab(tab, true);
    }

    private void setSelectedTabView(int position) {
        final int tabCount = mTabStrip.getChildCount();
        if (position < tabCount && !mTabStrip.getChildAt(position).isSelected()) {
            for (int i = 0; i < tabCount; i++) {
                final View child = mTabStrip.getChildAt(i);
                child.setSelected(i == position);
            }
        }
    }

    public void setScrollPosition(int position, float positionOffset, boolean updateSelectedText) {
        setScrollPosition(position, positionOffset, updateSelectedText, true);
    }
    private void setScrollPosition(int position, float positionOffset, boolean updateSelectedText,
                                   boolean updateIndicatorPosition) {
        final int roundedPosition = Math.round(position + positionOffset);
        if (roundedPosition < 0 || roundedPosition >= mTabStrip.getChildCount()) {
            return;
        }

        // Set the indicator position, if enabled
        if (updateIndicatorPosition) {
            mTabStrip.setIndicatorPositionFromTabPosition(position, positionOffset);
        }

        // Now update the scroll position, canceling any running animation
        if (mScrollAnimator != null && mScrollAnimator.isRunning()) {
            mScrollAnimator.cancel();
        }
        scrollTo(calculateScrollXForTab(position, positionOffset), 0);

        // Update the 'selected state' view as we scroll, if enabled
        if (updateSelectedText) {
            setSelectedTabView(roundedPosition);
        }
    }


    public void setupWithViewPager(@Nullable final ViewPager viewPager) {
        if (mViewPager != null && mPageChangeListener != null) {
            // If we've already been setup with a ViewPager, remove us from it
            mViewPager.removeOnPageChangeListener(mPageChangeListener);
        }

        if (viewPager != null) {
            final PagerAdapter adapter = viewPager.getAdapter();
            if (adapter == null) {
                throw new IllegalArgumentException("ViewPager does not have a PagerAdapter set");
            }

            mViewPager = viewPager;

            // Add our custom OnPageChangeListener to the ViewPager
            if (mPageChangeListener == null) {
                mPageChangeListener = new TabLayoutOnPageChangeListener(this);
            }
            mPageChangeListener.reset();
            viewPager.addOnPageChangeListener(mPageChangeListener);

            // Now we'll add a tab selected listener to set ViewPager's current item
            setOnTabSelectedListener(new ViewPagerOnTabSelectedListener(viewPager));

            // Now we'll populate ourselves from the pager adapter
            setPagerAdapter(adapter, true);
        } else {
            // We've been given a null ViewPager so we need to clear out the internal state,
            // listeners and observers
            mViewPager = null;
            setOnTabSelectedListener(null);
            setPagerAdapter(null, true);
        }
    }

    /**
     * Set the {@link OnTabSelectedListener} that will
     * handle switching to and from tabs.
     *
     * @param onTabSelectedListener Listener to handle tab selection events
     */
    public void setOnTabSelectedListener(OnTabSelectedListener onTabSelectedListener) {
        mOnTabSelectedListener = onTabSelectedListener;
    }

    /**
     * Returns the tab at the specified index.
     */
    @Nullable
    public ITab getTabAt(int index) {
        return mTabs.get(index);
    }

    /**
     * Returns the position of the current selected tab.
     *
     * @return selected tab position, or {@code -1} if there isn't a selected tab.
     */
    public int getSelectedTabPosition() {
        return mSelectedTab != null ? mSelectedTab.getPosition() : -1;
    }
    /**
     * Remove all tabs from the action bar and deselect the current tab.
     */
    public void removeAllTabs() {
        // Remove all the views
        for (int i = mTabStrip.getChildCount() - 1; i >= 0; i--) {
            removeTabViewAt(i);
        }

        for (final Iterator<ITab> i = mTabs.iterator(); i.hasNext();) {
            final ITab tab = i.next();
            i.remove();
            tab.reset();
            mTabPool.release(tab);
        }

        mSelectedTab = null;
    }

    private void removeTabViewAt(int position) {
        final ITabView view = (ITabView) mTabStrip.getChildAt(position);
        mTabStrip.removeViewAt(position);
        if (view != null) {
            view.reset();
            mTabViewPool.release(view);
        }
        requestLayout();
    }

    /**
     * Returns the number of tabs currently registered with the action bar.
     *
     * @return Tab count
     */
    public int getTabCount() {
        return mTabs.size();
    }



    void selectTab(ITab tab, boolean updateIndicator) {
        if (mSelectedTab == tab) {
            if (mSelectedTab != null) {
                if (mOnTabSelectedListener != null) {
                    mOnTabSelectedListener.onTabReselected(mSelectedTab);
                }
                animateToTab(tab.getPosition());
            }
        } else {
            if (updateIndicator) {
                final int newPosition = tab != null ? tab.getPosition() : ITab.INVALID_POSITION;
                if (newPosition != ITab.INVALID_POSITION) {
                    setSelectedTabView(newPosition);
                }
                if ((mSelectedTab == null || mSelectedTab.getPosition() == ITab.INVALID_POSITION)
                        && newPosition != ITab.INVALID_POSITION) {
                    // If we don't currently have a tab, just draw the indicator
                    setScrollPosition(newPosition, 0f, true);
                } else {
                    animateToTab(newPosition);
                }
            }
            if (mSelectedTab != null && mOnTabSelectedListener != null) {
                mOnTabSelectedListener.onTabUnselected(mSelectedTab);
            }
            mSelectedTab = tab;
            if (mSelectedTab != null && mOnTabSelectedListener != null) {
                mOnTabSelectedListener.onTabSelected(mSelectedTab);
            }
        }
    }

    private void animateToTab(int newPosition) {
        if (newPosition == ITab.INVALID_POSITION) {
            return;
        }

        if (getWindowToken() == null || !ViewCompat.isLaidOut(this)
                || mTabStrip.childrenNeedLayout()) {
            // If we don't have a window token, or we haven't been laid out yet just draw the new
            // position now
            setScrollPosition(newPosition, 0f, true);
            return;
        }

        final int startScrollX = getScrollX();
        final int targetScrollX = calculateScrollXForTab(newPosition, 0);

        if (startScrollX != targetScrollX) {
            if (mScrollAnimator == null) {
                mScrollAnimator = new ValueAnimator();
//                AnimatorCompatHelper.emptyValueAnimator();
                mScrollAnimator.setDuration(ANIMATION_DURATION);
                mScrollAnimator.setInterpolator(interpolator);
                mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        int currentScrollX = (int)animation.getAnimatedValue();;
                        scrollTo(currentScrollX, 0);
                    }
                });
            }

            mScrollAnimator.setIntValues(startScrollX, targetScrollX);
            mScrollAnimator.start();
        }

        // Now animate the indicator
        mTabStrip.animateIndicatorToPosition(newPosition, ANIMATION_DURATION);
    }

    private int calculateScrollXForTab(int position, float positionOffset) {
        if (mMode == MODE_SCROLLABLE) {
            final View selectedChild = mTabStrip.getChildAt(position);
            final View nextChild = position + 1 < mTabStrip.getChildCount()
                    ? mTabStrip.getChildAt(position + 1)
                    : null;
            final int selectedWidth = selectedChild != null ? selectedChild.getWidth() : 0;
            final int nextWidth = nextChild != null ? nextChild.getWidth() : 0;

            return selectedChild.getLeft()
                    + ((int) ((selectedWidth + nextWidth) * positionOffset * 0.5f))
                    + (selectedChild.getWidth() / 2)
                    - (getWidth() / 2);
        }
        return 0;
    }
    public static class ITab {
        /**
         * An invalid position for a tab.
         *
         * @see #getPosition()
         */
        public static final int INVALID_POSITION = -1;

        private Object mTag;

        private ITabLayout mParent;
        private ITabView mView;
        private CharSequence mText;
        private Drawable mIcon;
        private int mIconSize;
        private int mOrientation;
        private int mTextSize;
        private ColorStateList mTextColors;
        private int mIconLeftMargin;
        private int mIconTopMargin;
        private int mIconRightMargin;
        private int mIconBottomMargin;
        private int mLeftPadding;
        private int mTopPadding;
        private int mRightPadding;
        private int mBottomPadding;

        private int mPosition = INVALID_POSITION;

        private int mTextAppearance;
        private ITab(){}

        private void reset() {
            mParent = null;
            mView = null;
            mTag = null;
            mIcon = null;
            mText = null;
            mTextColors = null;
            mPosition = INVALID_POSITION;
            mIconSize = 0;
            mTextSize = 0;
            mIconLeftMargin = 0;
            mIconTopMargin = 0;
            mIconRightMargin = 0;
            mIconBottomMargin = 0;
            mLeftPadding = 0;
            mTopPadding = 0;
            mRightPadding = 0;
            mBottomPadding = 0;
            mTextAppearance = 0;
        }

        public ITab setTag(Object tag) {
            this.mTag = tag;
            return this;
        }

        public Object getTag() {
            return mTag;
        }

        public ITab setText(CharSequence text) {
            mText = text;
            return this;
        }

        public ITab setIcon(Drawable icon) {
            mIcon = icon;
            return this;
        }

        public ITab setOrientation(int mOrientation) {
            this.mOrientation = mOrientation;
            return this;
        }

        public ITab setTextAppearance(int mTextAppearance) {
            this.mTextAppearance = mTextAppearance;
            return this;
        }

        public ITab setTextSize(int mTextSize) {
            this.mTextSize = mTextSize;
            return this;
        }

        public ITab setTextColors(ColorStateList mTextColors) {
            this.mTextColors = mTextColors;
            return this;
        }

        public ITab setIconSize(int iconSize) {
            this.mIconSize = iconSize;
            return this;
        }

        public ITab setIconLeftMargin(int mIconLeftMargin) {
            this.mIconLeftMargin = mIconLeftMargin;
            return this;
        }

        public ITab setIconTopMargin(int mIconTopMargin) {
            this.mIconTopMargin = mIconTopMargin;
            return this;
        }

        public ITab setIconRightMargin(int mIconRightMargin) {
            this.mIconRightMargin = mIconRightMargin;
            return this;
        }

        public ITab setIconBottomMargin(int mIconBottomMargin) {
            this.mIconBottomMargin = mIconBottomMargin;
            return this;
        }

        public ITab setLeftPadding(int mLeftPadding) {
            this.mLeftPadding = mLeftPadding;
            return this;
        }

        public ITab setTopPadding(int mTopPadding) {
            this.mTopPadding = mTopPadding;
            return this;
        }

        public ITab setRightPadding(int mRightPadding) {
            this.mRightPadding = mRightPadding;
            return this;
        }

        public ITab setBottomPadding(int mBottomPadding) {
            this.mBottomPadding = mBottomPadding;
            return this;
        }

        public ITab updateTabView() {
            if (null != mView) {
                mView.update();
            }
            return this;
        }

        /**
         * Return the current position of this tab in the action bar.
         *
         * @return Current position, or {@link #INVALID_POSITION} if this tab is not currently in
         * the action bar.
         */
        public int getPosition() {
            return mPosition;
        }

        ITab setPosition(int position) {
            mPosition = position;
            return this;
        }

        /**
         * Select this tab. Only valid if the tab has been added to the action bar.
         */
        public void select() {
            if (mParent == null) {
                throw new IllegalArgumentException("Tab not attached to a TabLayout");
            }
            mParent.selectTab(this);
        }
    }


    /**
     * Item view
     */
    private class ITabView extends LinearLayout {

        private ITab mTab;
        private ImageView mIconView;
        private TextView mTextView;

        public ITabView(Context context) {
            super(context);
            setGravity(Gravity.CENTER);
            setClickable(true);
        }

        private void reset() {
            setTab(null);
            setSelected(false);
        }

        private void setTab(@Nullable final ITab tab) {
            if (tab != mTab) {
                mTab = tab;
                update();
            }
        }

        @Override
        public boolean performClick() {
            final boolean value = super.performClick();

            if (mTab != null) {
                mTab.select();
                return true;
            } else {
                return value;
            }
        }

        @Override
        public void setSelected(boolean selected) {
            final boolean changed = (isSelected() != selected);
            super.setSelected(selected);
            if (changed && selected) {
                sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);

                if (mTextView != null) {
                    mTextView.setSelected(selected);
                }
                if (mIconView != null) {
                    mIconView.setSelected(selected);
                }
            }
        }
        public void update() {
            final ITab tab = mTab;
            if (tab == null) {
                if (null != mIconView) {
                    mIconView.setImageDrawable(null);
                    mIconView.setVisibility(View.GONE);
                }
                if (null != mTextView) {
                    mTextView.setText(null);
                    mTextView.setVisibility(View.GONE);
                }
                return;
            }

            // icon update
            if (null == mIconView) {
                mIconView = new ImageView(getContext());
                addView(mIconView, 0, new LayoutParams(tab.mIconSize,
                        tab.mIconSize));
            }
            // update icon params
            LayoutParams lp = (LayoutParams) mIconView.getLayoutParams();
            lp.width = tab.mIconSize;
            lp.height = tab.mIconSize;
            lp.topMargin = tab.mIconTopMargin;
            lp.bottomMargin = tab.mIconBottomMargin;
            lp.leftMargin = tab.mIconLeftMargin;
            lp.rightMargin = tab.mIconRightMargin;
            mIconView.setLayoutParams(lp);

            Drawable icon = tab.mIcon;
            if (null == icon) {
                mIconView.setVisibility(View.GONE);
            } else {
                mIconView.setVisibility(View.VISIBLE);
            }
            mIconView.setImageDrawable(tab.mIcon);

            // text update
            if (null == mTextView) {
                mTextView = new TextView(getContext());
                mTextView.setGravity(Gravity.CENTER);
                mTextView.setSingleLine();
                addView(mTextView, new LayoutParams(LayoutParams.WRAP_CONTENT,
                        LayoutParams.WRAP_CONTENT));
            }
            if (tab.mTextAppearance > 0) {
                mTextView.setTextAppearance(getContext(), tab.mTextAppearance);
            }

            mTextView.setText(tab.mText);
            mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, tab.mTextSize);
            if (null != tab.mTextColors) {
                mTextView.setTextColor(tab.mTextColors);
            }

            setPadding(tab.mLeftPadding, tab.mTopPadding, tab.mRightPadding, tab.mBottomPadding);
            setOrientation(tab.mOrientation);
        }
    }

    /**
     * Sliding Tab Strip
     */
    private class ISlidingTabStrip extends LinearLayout {
        private final int MOTION_NON_ADJACENT_OFFSET = 24;

        private int mSelectedIndicatorHeight;
        private final Paint mSelectedIndicatorPaint;

        private int mSelectedPosition = -1;
        private float mSelectionOffset;

        private int mIndicatorLeft = -1;
        private int mIndicatorRight = -1;

        private ValueAnimator mIndicatorAnimator;

        public ISlidingTabStrip(Context context) {
            super(context);
            setWillNotDraw(false);
            mSelectedIndicatorPaint = new Paint();
        }

        void setSelectedIndicatorColor(int color) {
            if (mSelectedIndicatorPaint.getColor() != color) {
                mSelectedIndicatorPaint.setColor(color);
                ViewCompat.postInvalidateOnAnimation(this);
            }
        }

        void setSelectedIndicatorHeight(int height) {
            if (mSelectedIndicatorHeight != height) {
                mSelectedIndicatorHeight = height;
                ViewCompat.postInvalidateOnAnimation(this);
            }
        }
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }

        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            super.onLayout(changed, l, t, r, b);
            if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {
                // If we're currently running an animation, lets cancel it and start a
                // new animation with the remaining duration
                mIndicatorAnimator.cancel();
//                final long duration = mIndicatorAnimator.getDuration();
                animateIndicatorToPosition(mSelectedPosition,
                        Math.round((1f - mIndicatorAnimator.getAnimatedFraction()) * ANIMATION_DURATION));
            } else {
                // If we've been layed out, update the indicator position
                updateIndicatorPosition();
            }
        }
        void setIndicatorPositionFromTabPosition(int position, float positionOffset) {
            if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {
                mIndicatorAnimator.cancel();
            }

            mSelectedPosition = position;
            mSelectionOffset = positionOffset;
            updateIndicatorPosition();
        }
        boolean childrenNeedLayout() {
            for (int i = 0, z = getChildCount(); i < z; i++) {
                final View child = getChildAt(i);
                if (child.getWidth() <= 0) {
                    return true;
                }
            }
            return false;
        }
        private void updateIndicatorPosition() {
            final View selectedTitle = getChildAt(mSelectedPosition);
            int left, right;

            if (selectedTitle != null && selectedTitle.getWidth() > 0) {
                left = selectedTitle.getLeft();
                right = selectedTitle.getRight();

                if (mSelectionOffset > 0f && mSelectedPosition < getChildCount() - 1) {
                    // Draw the selection partway between the tabs
                    View nextTitle = getChildAt(mSelectedPosition + 1);
                    left = (int) (mSelectionOffset * nextTitle.getLeft() +
                            (1.0f - mSelectionOffset) * left);
                    right = (int) (mSelectionOffset * nextTitle.getRight() +
                            (1.0f - mSelectionOffset) * right);
                }
            } else {
                left = right = -1;
            }

            setIndicatorPosition(left, right);
        }

        void animateIndicatorToPosition(final int position, int duration) {
            if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {
                mIndicatorAnimator.cancel();
            }

            final boolean isRtl = ViewCompat.getLayoutDirection(this)
                    == ViewCompat.LAYOUT_DIRECTION_RTL;

            final View targetView = getChildAt(position);
            if (targetView == null) {
                // If we don't have a view, just update the position now and return
                updateIndicatorPosition();
                return;
            }

            final int targetLeft = targetView.getLeft();
            final int targetRight = targetView.getRight();
            final int startLeft;
            final int startRight;

            if (Math.abs(position - mSelectedPosition) <= 1) {
                // If the views are adjacent, we'll animate from edge-to-edge
                startLeft = mIndicatorLeft;
                startRight = mIndicatorRight;
            } else {
                // Else, we'll just grow from the nearest edge
                final int offset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                        (float) MOTION_NON_ADJACENT_OFFSET, mDm);
                if (position < mSelectedPosition) {
                    // We're going end-to-start
                    if (isRtl) {
                        startLeft = startRight = targetLeft - offset;
                    } else {
                        startLeft = startRight = targetRight + offset;
                    }
                } else {
                    // We're going start-to-end
                    if (isRtl) {
                        startLeft = startRight = targetRight + offset;
                    } else {
                        startLeft = startRight = targetLeft - offset;
                    }
                }
            }

            if (startLeft != targetLeft || startRight != targetRight) {
                ValueAnimator animator = mIndicatorAnimator = new ValueAnimator();
                animator.setDuration(duration);
                animator.setFloatValues(0, 1);
                animator.setInterpolator(interpolator);
                final int distanceLeft = targetLeft - startLeft;
                final int distanceRight = targetRight - startRight;
                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        final float fraction = animation.getAnimatedFraction();
                        int currentLeft = Math.round(startLeft + distanceLeft * fraction);
                        int currentRight = Math.round(startRight + distanceRight * fraction);
                        setIndicatorPosition(currentLeft, currentRight);
                    }
                });
                animator.addListener(new AnimatorListenerAdapter() {

                    @Override
                    public void onAnimationEnd(Animator animation) {
                        mSelectedPosition = position;
                        mSelectionOffset = 0f;
                    }
                });
                animator.start();
            }
        }

        private void setIndicatorPosition(int left, int right) {
            if (left != mIndicatorLeft || right != mIndicatorRight) {
                // If the indicator's left/right has changed, invalidate
                mIndicatorLeft = left;
                mIndicatorRight = right;
                ViewCompat.postInvalidateOnAnimation(this);
            }
        }
        @Override
        public void draw(Canvas canvas) {
            super.draw(canvas);

            // Thick colored underline below the current selection
            if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {
                final int height = getHeight();
                canvas.drawRect(mIndicatorLeft, height - mSelectedIndicatorHeight,
                        mIndicatorRight, height, mSelectedIndicatorPaint);
            }
        }
    }
    /**
     * Callback interface invoked when a tab's selection state changes.
     */
    public interface OnTabSelectedListener {

        /**
         * Called when a tab enters the selected state.
         *
         * @param tab The tab that was selected
         */
        public void onTabSelected(ITab tab);

        /**
         * Called when a tab exits the selected state.
         *
         * @param tab The tab that was unselected
         */
        public void onTabUnselected(ITab tab);

        /**
         * Called when a tab that is already selected is chosen again by the user. Some applications
         * may use this action to return to the top level of a category.
         *
         * @param tab The tab that was reselected.
         */
        public void onTabReselected(ITab tab);
    }

    static class AnimatorListenerAdapter implements Animator.AnimatorListener {
        @Override
        public void onAnimationStart(Animator animation) {

        }

        @Override
        public void onAnimationEnd(Animator animation) {

        }

        @Override
        public void onAnimationCancel(Animator animation) {

        }

        @Override
        public void onAnimationRepeat(Animator animation) {

        }
    }


    /**
     * A {@link ViewPager.OnPageChangeListener} class which contains the
     * necessary calls back to the provided {@link ITabLayout} so that the tab position is
     * kept in sync.
     *
     * <p>This class stores the provided TabLayout weakly, meaning that you can use
     * {@link ViewPager#addOnPageChangeListener(ViewPager.OnPageChangeListener)
     * addOnPageChangeListener(OnPageChangeListener)} without removing the listener and
     * not cause a leak.
     */
    public static class TabLayoutOnPageChangeListener implements ViewPager.OnPageChangeListener {
        private final WeakReference<ITabLayout> mTabLayoutRef;
        private int mPreviousScrollState;
        private int mScrollState;

        public TabLayoutOnPageChangeListener(ITabLayout tabLayout) {
            mTabLayoutRef = new WeakReference<>(tabLayout);
        }

        @Override
        public void onPageScrollStateChanged(int state) {
            mPreviousScrollState = mScrollState;
            mScrollState = state;
        }

        @Override
        public void onPageScrolled(int position, float positionOffset,
                                   int positionOffsetPixels) {
            final ITabLayout tabLayout = mTabLayoutRef.get();
            if (tabLayout != null) {
                // Only update the text selection if we're not settling, or we are settling after
                // being dragged
                final boolean updateText = mScrollState != SCROLL_STATE_SETTLING ||
                        mPreviousScrollState == SCROLL_STATE_DRAGGING;
                // Update the indicator if we're not settling after being idle. This is caused
                // from a setCurrentItem() call and will be handled by an animation from
                // onPageSelected() instead.
                final boolean updateIndicator = !(mScrollState == SCROLL_STATE_SETTLING
                        && mPreviousScrollState == SCROLL_STATE_IDLE);
                tabLayout.setScrollPosition(position, positionOffset, updateText, updateIndicator);
            }
        }

        @Override
        public void onPageSelected(int position) {
            final ITabLayout tabLayout = mTabLayoutRef.get();
            if (tabLayout != null && tabLayout.getSelectedTabPosition() != position) {
                // Select the tab, only updating the indicator if we're not being dragged/settled
                // (since onPageScrolled will handle that).
                final boolean updateIndicator = mScrollState == SCROLL_STATE_IDLE
                        || (mScrollState == SCROLL_STATE_SETTLING
                        && mPreviousScrollState == SCROLL_STATE_IDLE);
                tabLayout.selectTab(tabLayout.getTabAt(position), updateIndicator);
            }
        }

        private void reset() {
            mPreviousScrollState = mScrollState = SCROLL_STATE_IDLE;
        }
    }

    /**
     * A {@link OnTabSelectedListener} class which contains the necessary calls back
     * to the provided {@link ViewPager} so that the tab position is kept in sync.
     */
    public static class ViewPagerOnTabSelectedListener implements OnTabSelectedListener {
        private final ViewPager mViewPager;

        public ViewPagerOnTabSelectedListener(ViewPager viewPager) {
            mViewPager = viewPager;
        }

        @Override
        public void onTabSelected(ITab tab) {
            mViewPager.setCurrentItem(tab.getPosition());
        }

        @Override
        public void onTabUnselected(ITab tab) {
            // No-op
        }

        @Override
        public void onTabReselected(ITab tab) {
            // No-op
        }
    }

    private class PagerAdapterObserver extends DataSetObserver {
        @Override
        public void onChanged() {
            populateFromPagerAdapter();
        }

        @Override
        public void onInvalidated() {
            populateFromPagerAdapter();
        }
    }

    private void setPagerAdapter(@Nullable final PagerAdapter adapter, final boolean addObserver) {
        if (mPagerAdapter != null && mPagerAdapterObserver != null) {
            // If we already have a PagerAdapter, unregister our observer
            mPagerAdapter.unregisterDataSetObserver(mPagerAdapterObserver);
        }

        mPagerAdapter = adapter;

        if (addObserver && adapter != null) {
            // Register our observer on the new adapter
            if (mPagerAdapterObserver == null) {
                mPagerAdapterObserver = new PagerAdapterObserver();
            }
            adapter.registerDataSetObserver(mPagerAdapterObserver);
        }

        // Finally make sure we reflect the new adapter
        populateFromPagerAdapter();
    }


    private void populateFromPagerAdapter() {
        removeAllTabs();

        if (mPagerAdapter != null) {
            final int adapterCount = mPagerAdapter.getCount();
            for (int i = 0; i < adapterCount; i++) {
                ITabItem  item = new ITabItem(getContext());
                ITab tab = newTab();
                updateTab(tab, item);
                tab.setText(mPagerAdapter.getPageTitle(i));
                tab.updateTabView();
                addTab(tab, false);
            }

            // Make sure we reflect the currently set ViewPager item
            if (mViewPager != null && adapterCount > 0) {
                final int curItem = mViewPager.getCurrentItem();
                if (curItem != getSelectedTabPosition() && curItem < getTabCount()) {
                    selectTab(getTabAt(curItem));
                }
            }
        } else {
            removeAllTabs();
        }
    }
}
